/**
  @file App.cpp
 */
#include "App.h"

// Tells C++ to invoke command-line main() function even on OS X and Win32.
G3D_START_AT_MAIN();

int main(int argc, char** argv) {
    (void)argc; (void)argv;
    GApp::Settings settings;
    
    // Change the window and other startup parameters by modifying the
    // settings class.  For example:
    settings.window.width       = 1280; 
    settings.window.height      = 800;
	settings.dataDir            = "data";

#   ifdef G3D_WIN32
        // On unix operating systems, icompile automatically copies data files.  
        // On Windows, we just run from the data directory.
        if (FileSystem::exists("data")) {
            chdir("data");
        }
#   endif

    return App(settings).run();
}


App::App(const GApp::Settings& settings) : GApp(settings) {
#   ifdef G3D_DEBUG
        // Let the debugger catch unhandled exceptions
        catchCommonExceptions = false;
#   endif
	//setDesiredFrameRate(60);
}


void App::onInit() {
    // Called before the application loop beings.  Load data here and
    // not in the constructor so that common exceptions will be
    // automatically caught.

    // Turn on the developer HUD
    debugWindow->setVisible(true);
    developerWindow->cameraControlWindow->setVisible(true);
    developerWindow->videoRecordDialog->setEnabled(true);
    showRenderingStats = true;

    // Start wherever the developer HUD last marked as "Home"
    defaultCamera.setCoordinateFrame(bookmark("Home"));

	// init OpenGL
	//envmap = Texture::fromFile("cubemap/noonclouds/noonclouds_*.png", ImageFormat::RGB8(), Texture::DIM_CUBE_MAP, Texture::Settings::cubeMap(), Texture::Preprocess::gamma(2.1f));
	//envmap = Texture::fromFile("cubemap/test/testcube_*.jpg", ImageFormat::RGB8(), Texture::DIM_CUBE_MAP, Texture::Settings::cubeMap(), Texture::Preprocess::gamma(2.1f));
	envmap = Texture::fromFile("cubemap/hourglass/hourglass_*.jpg", ImageFormat::RGB8(), Texture::DIM_CUBE_MAP, Texture::Settings::cubeMap(), Texture::Preprocess::gamma(1.1f));
	//envmap = Texture::fromFile("cubemap/museum/museum_*.jpg", ImageFormat::RGB8(), Texture::DIM_CUBE_MAP, Texture::Settings::cubeMap());

	// init user interface
	addWidget(editor.window);
	addWidget(viewer.window);
	addWidget(viewer.sliceWindow.window);
	editor.window->moveTo(viewer.window->rect().x1y0());
	debugWindow->moveTo(editor.window->rect().x0y1());
	viewer.sliceWindow.window->setVisible(false);
	developerWindow->cameraControlWindow->setVisible(false);
	showDebugText = false;
	showRenderingStats = false;
	//solver.resize(100, 200, 100, 1.0f / 100.0f);
	solver.accTemp = 10.0f;
	solver.accTurb = 25.0f;
	solver.burnRate = 50.0f;
	solver.coolRate = 10.0f;
	solver.resize(64, 64, 64, 1.0f / 64.0f);
	sourceRadius = 0.125f;
	sourceCenter = Vector3(0.5f, 0.05f, 0.5f);
	viewer.renderer.valueScale = 1.0f;
	volume.update(solver.nx, solver.ny, solver.nz, solver.t->ptr);
	tex = volume.tex;

	time = 0.0f;
	timeStep = 0.005f;
	simulating = false;
	indirect = false;
	debugWindow->setCaption("Control Panel");
	debugPane->addCheckBox("Simulating", &simulating);
	(debugPane->addCheckBox("Indirect [i]", &indirect))->setEnabled(false);
	fieldIdx = 0;
	Array<std::string> fieldStrings;
	fieldStrings.push_back("Fuel Density");
	fieldStrings.push_back("Soot Density");
	fieldStrings.push_back("Temperature");
	fieldStrings.push_back("Velocity U");
	fieldStrings.push_back("Velocity V");
	fieldStrings.push_back("Velocity W");
	fieldStrings.push_back("External Data");
	fieldStrings.push_back("Photon Volume");
	fieldList = debugPane->addDropDownList("Field:", fieldStrings, &fieldIdx);
	//fieldList->setWidth(200);
	debugPane->addButton("Save Field", this, &App::saveField)->moveRightOf(fieldList);
	l = m = 0;
	ki = 1.0f;
	brt.lighting.values.resize(4);
	brt.lighting.values[0] = 1.0f;
	brt.lighting.values[1] = 1.0f;
	brt.lighting.values[2] = 0.0f;
	brt.lighting.values[3] = 0.0f;
	(debugPane->addNumberBox("Ki:", &ki, "", GuiTheme::LOG_SLIDER, 0.0f, 100.0f))->setWidth(400);
	(debugPane->addNumberBox("Theta:", &theta, "", GuiTheme::LINEAR_SLIDER, 0.0f, SHFunction<float>::PI))->setWidth(400);
	(debugPane->addNumberBox("Phi:", &phi, "", GuiTheme::LINEAR_SLIDER, 0.0f, SHFunction<float>::PI * 2.0f))->setWidth(400);
	(debugPane->addNumberBox("SH-Y-l:", &l, "", GuiTheme::LINEAR_SLIDER, 0, 10))->setWidth(400);
	(debugPane->addNumberBox("SH-Y-m:", &m, "", GuiTheme::LINEAR_SLIDER, -10, 10))->setWidth(400);
	(debugPane->addNumberBox("P-Samples:", &brt.psamples, "", GuiTheme::LINEAR_SLIDER, 0, 1024))->setWidth(400);
	(debugPane->addNumberBox("R-Samples:", &brt.rsamples, "", GuiTheme::LINEAR_SLIDER, 0, 1024))->setWidth(400);
	(debugPane->addNumberBox("P-Bands:", &brt.pbands, "", GuiTheme::LINEAR_SLIDER, 0, 10))->setWidth(400);
	(debugPane->addNumberBox("R-Bands:", &brt.rbands, "", GuiTheme::LINEAR_SLIDER, 0, 10))->setWidth(400);
	(debugPane->addNumberBox("Absorption:", &brt.siga, "", GuiTheme::LOG_SLIDER, 0.0f, 1000.0f))->setWidth(400);	brt.siga = 10.0f;
	(debugPane->addNumberBox("Scattering:", &brt.sigs, "", GuiTheme::LOG_SLIDER, 0.0f, 1000.0f))->setWidth(400);	brt.sigs = 10.0f;
	(debugPane->addNumberBox("Time Step:", &timeStep, "", GuiTheme::LOG_SLIDER, 0.0001f, 0.01f))->setWidth(400);
	(debugPane->addNumberBox("Bouyancy:", &solver.accTemp, "", GuiTheme::LOG_SLIDER, 0.0f, 100.0f))->setWidth(400);
	(debugPane->addNumberBox("Turbulence:", &solver.accTurb, "", GuiTheme::LOG_SLIDER, 0.0f, 100.0f))->setWidth(400);
	(debugPane->addNumberBox("Reaction:", &solver.burnRate, "", GuiTheme::LOG_SLIDER, 0.0f, 100.0f))->setWidth(400);
	(debugPane->addNumberBox("Cooling:", &solver.coolRate, "", GuiTheme::LOG_SLIDER, 0.0f, 100.0f))->setWidth(400);
	(debugPane->addNumberBox("Wind X:", &solver.uBC, "", GuiTheme::LOG_SLIDER, 0.0f, 10.0f))->setWidth(400);
	(debugPane->addNumberBox("Wind Y:", &solver.vBC, "", GuiTheme::LOG_SLIDER, 0.0f, 10.0f))->setWidth(400);
	(debugPane->addNumberBox("Wind Z:", &solver.wBC, "", GuiTheme::LOG_SLIDER, 0.0f, 10.0f))->setWidth(400);
	debugPane->pack();
	debugWindow->pack();
}


void App::onAI() {
    // Add non-simulation game logic and AI code here
}


void App::onNetwork() {
    // Poll net messages here
}


void App::onSimulation(RealTime rdt, SimTime sdt, SimTime idt) {
    (void)idt; (void)sdt; (void)rdt;
    // Add physical simulation here.  You can make your time
    // advancement based on any of the three arguments.
	if(simulating)
	{
		//solver.drawRectangle(2.0f, 0.05f, 0.25f, 3.0f, 0.05f, 0.1f, 1.0f, solver.f);
		solver.drawCylinder(sourceCenter.x, sourceCenter.y, sourceCenter.z, sourceRadius, 0.025f, 1.0f, solver.f);
		//solver.drawSphere(0.5f, 0.25f, 0.5f, 0.2f, 1.0f, solver.f);
		solver.step(timeStep);
		std::cout.precision(3);
		std::cout.setf(std::ios::floatfield, std::ios::fixed);
		std::cout << "Time: " << (time += timeStep) << std::endl;
		//viewer.renderer.valueScale = 1.0f;
		//viewer.renderer.valueScale = 1.0f / solver.maxTemp;
		std::cout << "max{T}: " << solver.maxTemp << std::endl;
		updateVolumeTexture();
		//simulating = false;
	}
}


bool App::onEvent(const GEvent& e)
{
	if(e.type == GEventType::GUI_ACTION)
	{
		if(m < -l)
			m = -l;
		if(m > l)
			m = l;
		editor.editor->update();
		if(e.gui.control == fieldList)
			updateVolumeTexture();
	}

	if(e.type == GEventType::FILE_DROP)
	{
		Array<std::string> files;
		window()->getDroppedFilenames(files);
		if(files[0].substr(files[0].size() - 4, 4) == ".dat")
		{
			field.load(files[0]);
			fieldIdx = 6;
			updateVolumeTexture();
		}
		else
			msgBox(".dat suffix required!");
	}

	return GApp::onEvent(e);
}


void App::onUserInput(UserInput* ui)
{
    // Add key handling here based on the keys currently held or
    // ones that changed in the last frame.
	//if(ui->keyPressed(' '))
	//{
	//	flow.step();
	//	flow.dump(u, v, w, d);
	//}
	if(ui->keyPressed(GKey::BACKSPACE))
	{
		solver.resize(solver.nx, solver.ny, solver.nz, solver.dx);
		//solver.drawSphere(0.5f, 0.25f, 0.5f, 0.2f, 1.0f, solver.t);
		volume.update(solver.nx, solver.ny, solver.nz, solver.t->ptr);
		simulating = false;
		time = 0.0f;
	}
	if(ui->keyPressed(GKey::SPACE))
		simulating = !simulating;
	if(ui->keyPressed('1'))
		editor.window->setVisible(!editor.window->visible());
	if(ui->keyPressed('2'))
		viewer.window->setVisible(!viewer.window->visible());
	if(ui->keyPressed('3'))
		viewer.sliceWindow.window->setVisible(!viewer.sliceWindow.window->visible());
	if(ui->keyPressed('t') && field.values.size() > 0)
	{
		brt.tracePhotons(field, l, m);
		//brt.updateVertexBuffer();
	}
	if(ui->keyPressed('r'))
		brt.kdtree.clear();
	if(ui->keyPressed('p'))
	{
		renderDevice->pushState();
		renderDevice->setCameraToWorldMatrix(viewer.viewer->frame.inverse());
		renderDevice->setObjectToWorldMatrix(CoordinateFrame());
		float h = 0.01f;
		float w = h * viewer.viewer->rect().width() / viewer.viewer->rect().height();
		renderDevice->setProjectionMatrix(Matrix4::perspectiveProjection(-w, w, -h, h, h * 2.0f, 100.0f));
		GCamera camera = renderDevice->projectionAndCameraMatrix();
		renderDevice->popState();

		Image1::Ref image = Image1::createEmpty(640, 480);
		Vector3 boxDim(volume.tex->width() * solver.dx, volume.tex->height() * solver.dx, volume.tex->depth() * solver.dx);
		boxDim *= (0.5f / boxDim.length());
		brt.renderScattering(field, -boxDim, boxDim, camera, image);
		image->save("preview.png");
	}
	if(ui->keyPressed('u'))
	{
		renderDevice->pushState();
		renderDevice->setCameraToWorldMatrix(viewer.viewer->frame.inverse());
		renderDevice->setObjectToWorldMatrix(CoordinateFrame());
		float h = 0.01f;
		float w = h * viewer.viewer->rect().width() / viewer.viewer->rect().height();
		renderDevice->setProjectionMatrix(Matrix4::perspectiveProjection(-w, w, -h, h, h * 2.0f, 100.0f));
		GCamera camera = renderDevice->projectionAndCameraMatrix();
		renderDevice->popState();

		Image1::Ref image = Image1::createEmpty(640, 480);
		Vector3 boxDim(volume.tex->width() * solver.dx, volume.tex->height() * solver.dx, volume.tex->depth() * solver.dx);
		boxDim *= (0.5f / boxDim.length());
		brt.render(field, -boxDim, boxDim, camera, image);
		image->save("render.png");
	}
	if(ui->keyPressed('i'))
	{
		indirect = !indirect;
		if(indirect)
		{
			viewer.renderer.shader = Shader::fromFiles("VolumeRendererGI.vp", "VolumeRendererGI.fp");
			assert(viewer.renderer.shader->ok());
			if(lhtmap.notNull())
				viewer.renderer.shader->args.set("LhtMap", lhtmap);
		}
		else
		{
			viewer.renderer.shader = Shader::fromFiles("VolumeRenderer.vp", "VolumeRenderer.fp");
			assert(viewer.renderer.shader->ok());
		}
	}
	if(ui->keyPressed('v'))	// load soot
	{
		field.load("soot.dat");
		fieldIdx = 6;
		updateVolumeTexture();
	}
	if(ui->keyPressed('e'))	// batch
	{
		time_t t0 = clock();
		//field.load("soot.dat");
		fieldIdx = 6;
		updateVolumeTexture();
		lmaps.resize(4);
		for(l = 0; l < 2; ++l) for(m = -l; m <= l; ++m)
		{
			int idx = l * l + l + m;
			char path[256];
			sprintf(path, "%s-sh%d", field.name.c_str(), idx);

			lmaps[idx].name = path;
			lmaps[idx].resize(field.nx, field.ny, field.nz, field.dx, field.type);
			brt.tracePhotons(field, l, m);
			brt.evaluateLightField(lmaps[idx]);
			lmaps[idx].save(path);
		}
		std::cout << (clock() - t0) << "ms" << std::endl;
	}
	if(ui->keyPressed('l'))
	{
		//lhtmap
		lmaps.resize(4);
		for(l = 0; l < 2; ++l) for(m = -l; m <= l; ++m)
		{
			int idx = l * l + l + m;
			char path[256];
			sprintf(path, "%s-sh%d", field.name.c_str(), idx);
			lmaps[idx].load(path);
		}

		Array<Color4uint8> lhtmapBuf;
		lhtmapBuf.resize(field.nx * field.ny * field.nz);
		for(int k = 0; k < field.nz; ++k) for(int j = 0; j < field.ny; ++j) for(int i = 0; i < field.nx; ++i)
		{
			int idx = i + j * field.nx + k * field.nx * field.ny;
			lhtmapBuf[idx] = Color4(lmaps[0][idx] * 0.5f + 0.5f, lmaps[1][idx] * 0.5f + 0.5f, lmaps[2][idx] * 0.5f + 0.5f, lmaps[3][idx] * 0.5f + 0.5f);
		}
		lhtmap = Texture::fromMemory("", &lhtmapBuf.front(), ImageFormat::RGBA8(), field.nx, field.ny, field.nz, ImageFormat::RGBA8(), Texture::DIM_3D_NPOT, Texture::Settings::video());
		if(indirect)
			viewer.renderer.shader->args.set("LhtMap", lhtmap);
	}
	if(ui->keyPressed('x'))
	{
		Image4uint8::Ref image = viewer.viewer->color->toImage4uint8();
		image->save("snapshot.jpg");
	}
}


void App::onPose(Array<Surface::Ref>& posed3D, Array<Surface2D::Ref>& posed2D) {
    // Append any models to the arrays that you want to later be rendered by onGraphics()
    (void)posed2D;
	//surfaceArray.append(model->pose());
//#ifdef ANIM_FIRE
//	knight->onPose(posed3D);
//	weapon->onPose(posed3D);
//#else
    (void)posed3D;
//#endif
}


void App::onGraphics3D(RenderDevice* rd, Array<Surface::Ref>& surface) {
	rd->setColorClearValue(Color4::zero());
	rd->clear(true, true, false);
	Draw::axes(CoordinateFrame(), rd);
	if(tex.notNull())
	{
		Vector3 boxDim(volume.tex->width() * solver.dx, volume.tex->height() * solver.dx, volume.tex->depth() * solver.dx);
		boxDim *= (0.5f / boxDim.length());
		viewer.renderBegin(rd, tex, editor.editor->colormap, -boxDim, boxDim);
		Vector3 ldir;
		SHFunction<float>::angleToVector(theta, phi, ldir.x, ldir.y, ldir.z);
		viewer.renderer.shader->args.set("LhtDir", ldir);
		if(indirect)
		{
			static float t0 = 0.0f;
			static float p0 = 0.0f;
			static SHFunction<float> shl(2);
			if(t0 != theta || p0 != phi)
			{
				const int samples = 65536;
				shl.values.assign(4, 0.0f);
				for(int i = 0; i < samples; ++i)
				{
					float ti, pi;
					shl.random(ti, pi);
					Vector3 di;
					shl.angleToVector(ti, pi, di.x, di.y, di.z);
					float lum = std::max(0.0f, -di.dot(ldir));
					shl.values[0] += lum * shl.realY(0, 0, ti, pi);
					shl.values[1] += lum * shl.realY(1,-1, ti, pi);
					shl.values[2] += lum * shl.realY(1, 0, ti, pi);
					shl.values[3] += lum * shl.realY(1, 1, ti, pi);
				}
				float coef = 4.0f * shl.PI / samples;
				shl.values[0] *= coef;
				shl.values[1] *= coef;
				shl.values[2] *= coef;
				shl.values[3] *= coef;
				t0 = theta;
				p0 = phi;
			}
			viewer.renderer.shader->args.set("Ki", ki);
			viewer.renderer.shader->args.set("LhtSHs", Vector4(shl.values[0], shl.values[1], shl.values[2], shl.values[3]));
		}
		Draw::skyBox(rd, envmap);
		Draw::arrow(-ldir, ldir * 0.5f, rd, Color3::yellow());
		brt.renderVertexBuffer(rd, -boxDim, boxDim * 2.0f / (Vector3(field.nx, field.ny, field.nz) * field.dx));
		viewer.renderEnd(rd, tex, editor.editor->colormap, -boxDim, boxDim);
	}
}


void App::onGraphics2D(RenderDevice* rd, Array<Surface2D::Ref>& posed2D) {
    // Render 2D objects like Widgets.  These do not receive tone mapping or gamma correction
    Surface2D::sortAndRender(rd, posed2D);
}


void App::onCleanup() {
    // Called after the application loop ends.  Place a majority of cleanup code
    // here instead of in the constructor so that exceptions can be caught
}
